#include "global.h"
#include "Threads_Wii.h"
#include "RageUtil.h"
#include <sys/time.h>
#include <errno.h>
#include <stdint.h>
#include <ogc/mutex.h>
#include <ogc/cond.h>

#define STACKSIZE (16*1024)

/// ThreadImpl ///

void ThreadImpl_Wii::Halt(bool kill) {
   LWP_SuspendThread(thread);
}

void ThreadImpl_Wii::Resume() {
   LWP_ResumeThread(thread);
}

uint64_t ThreadImpl_Wii::GetThreadId() const {
   return thread;
}

int ThreadImpl_Wii::Wait() {
   void *val;
   int ret = LWP_JoinThread(thread, &val);
   if(ret)
      RageException::Throw("LWP_JoinThread: %d", ret);
   
   return (int)val;
}

static void *StartThread(void *ptr) {
   static int startedThreads = 0;
   printf("Threads Started: %d", ++startedThreads);
   ThreadImpl_Wii *thread = (ThreadImpl_Wii*)ptr;
   
   thread->semaphore->Post();
   
   int ret = thread->function(thread->args);
   printf("Thread returned: %d", ret);
   return NULL;
}

ThreadImpl *MakeThread(int (*fn)(void *), void *data, uint64_t *piThreadID) {
   ThreadImpl_Wii *thread = new ThreadImpl_Wii;
   thread->function = fn;
   thread->args = data;
   thread->threadID = piThreadID;
   thread->semaphore = new SemaImpl_Wii(0);
   
   LWP_CreateThread(&thread->thread, StartThread, thread, 0, STACKSIZE, 0);
   
   thread->semaphore->Wait();
   delete thread->semaphore;
   
   return thread;
}

ThreadImpl *MakeThisThread() {
   ThreadImpl_Wii *thread = new ThreadImpl_Wii;
   thread->thread = LWP_GetSelf();
   
   return thread;
}

uint64_t GetThisThreadId() {
   return LWP_GetSelf();
}

uint64_t GetInvalidThreadId() {
   return 0;
}

//////////////////


/// MutexImpl ///

MutexImpl_Wii::MutexImpl_Wii(RageMutex *pParent) : MutexImpl(pParent) {
   LWP_MutexInit(&mMutex, false);
}

MutexImpl_Wii::~MutexImpl_Wii() {
   LWP_MutexDestroy(mMutex);
}

bool MutexImpl_Wii::Lock() {
   return LWP_MutexLock(mMutex) == 0;
}

bool MutexImpl_Wii::TryLock() {
   return LWP_MutexLock(mMutex) == 0;
}

void MutexImpl_Wii::Unlock() {
   LWP_MutexUnlock(mMutex);
}

MutexImpl *MakeMutex(RageMutex *pParent) {
   return new MutexImpl_Wii(pParent);
}

/////////////////

/// SemaImpl ///

SemaImpl_Wii::SemaImpl_Wii(int iInitialValue) {
   LWP_MutexInit(&mMutex, true);
   LWP_CondInit(&mCond);
   
   mValue = iInitialValue;
}

SemaImpl_Wii::~SemaImpl_Wii() {
   LWP_MutexDestroy(mMutex);
   LWP_CondDestroy(mCond);
}

void SemaImpl_Wii::Post() {
   LWP_MutexLock(mMutex);
   ++mValue;
   if(mValue == 1)
      LWP_CondSignal(mCond);
   LWP_MutexUnlock(mMutex);
}

bool SemaImpl_Wii::Wait() {
   LWP_MutexLock(mMutex);
   while(!mValue)
      LWP_CondWait(mCond, mMutex);
   
   --mValue;
   LWP_MutexUnlock(mMutex);
   
   return true;
}

bool SemaImpl_Wii::TryWait() {
   LWP_MutexLock(mMutex);
   if(!mValue) {
      LWP_MutexUnlock(mMutex);
      return false;
   }
   
   --mValue;
   LWP_MutexUnlock(mMutex);
   
   return true;
}

SemaImpl *MakeSemaphore(int iInitialValue) {
   return new SemaImpl_Wii(iInitialValue);
}

////////////////